package com.imgo.max.experience.service;

import com.alibaba.fastjson.JSON;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.imgo.max.cache.guava.ThreadPool;
import com.imgo.max.common.dto.ResultDO;
import com.imgo.max.common.log.CustomLogger;
import com.imgo.max.common.log.CustomLoggerFactory;
import com.imgo.max.common.result.ModuleResultCode;
import com.imgo.max.common.util.LoggerUtils;
import com.imgo.max.common.util.ValidatorUtils;
import com.imgo.max.experience.dto.UserExperienceDto;
import com.imgo.max.user.dto.User;
import com.imgo.max.user.service.UserCacheService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * 经验服务类，封装用户和练习生的经验查询
 *
 * @author peter.Y
 */
@Component
public class GeneralExperienceServiceImpl implements GeneralExperienceService {

    private static final CustomLogger logger = CustomLoggerFactory.getLogger(GeneralExperienceServiceImpl.class);

    @Resource
    PracticerExperienceHandler practicerExperienceHandler;

    @Resource
    UserExperienceHandler userExperienceHandler;

    @Resource
    UserCacheService userCacheService;

    private final static String LOCAL_CACHE_USER_ROLE_KEY_PREFIX = "role_";
    private final static String LOCAL_CACHE_USER_EXP_KEY_PREFIX = "exp_";

    /**
     * 查询用户和练习生的经验和等级，接口内部封装了用户和练习生的差异
     *
     * @param uids
     * @return
     */
    @Override
    public ResultDO<List<UserExperienceDto>> getUsersExperience(List<String> uids) {
        ResultDO<List<UserExperienceDto>> resultDO = ResultDO.buildResult();
        try {
            ResultDO<List<User>> userResult = userCacheService.queryUserByUids(uids);
            //练习生列表
            List<String> practicerUids = new ArrayList<String>();
            //普通用户列表
            List<String> userUids = new ArrayList<String>();
            classifyUser(userResult, practicerUids, userUids);

            List<UserExperienceDto> dtos = new ArrayList<UserExperienceDto>();
            ResultDO<List<UserExperienceDto>> usersExpr = userExperienceHandler.getUsersExperience(userUids);
            ResultDO<List<UserExperienceDto>> practicersExpr = practicerExperienceHandler.getUsersExperience(practicerUids);
            if (ResultDO.isSuccess(usersExpr) && usersExpr.getModule() != null) {
                fillHasOrNoExprUser(userUids, dtos, usersExpr.getModule(), "0");
            }
            if (ResultDO.isSuccess(practicersExpr) && practicersExpr.getModule() != null) {
                fillHasOrNoExprUser(practicerUids, dtos, practicersExpr.getModule(), "1");
            }
            resultDO.setModule(dtos);
        } catch (Exception e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uids);
        }
        return resultDO;
    }

    /**
     * 填充有经验记录和没有经验记录的用户到返回结果中
     *
     * @param uids
     * @param dtos
     * @param exprDtos
     * @param role
     */
    private void fillHasOrNoExprUser(List<String> uids, List<UserExperienceDto> dtos, List<UserExperienceDto> exprDtos, String role) {
        Map<String, UserExperienceDto> experienceDtoMap = new HashMap<>();
        for (UserExperienceDto expr : exprDtos) {
            if (expr != null && ValidatorUtils.isValidUid(expr.getUid())) {
                experienceDtoMap.put(expr.getUid(), expr);
            }
        }

        for (String uid : uids) {
            UserExperienceDto expr;
            UserExperienceDto tmpDto = experienceDtoMap.get(uid);
            if (tmpDto != null) {
                expr = tmpDto;
            } else {
                expr = new UserExperienceDto();
                expr.setUid(uid);
                expr.setLevel(1); //初始化就是1级
                expr.setExperience(0);
                expr.setRole(role);
            }
            dtos.add(expr);
        }
    }

    @Override
    public ResultDO<UserExperienceDto> getUserExperience(String uid) {
        ResultDO<UserExperienceDto> resultDO = ResultDO.buildResult();
        try {
            String roleCacheKey = getLocalUserRoleKey(uid);
            Integer userRole = (Integer) generalLocalCache.get(roleCacheKey);
            if (userRole != null) {
                String key = "exp_" + uid + "_" + userRole;
                resultDO = (ResultDO<UserExperienceDto>) generalLocalCache.get(key);
                logger.info("命中本地缓存：{}, {}", userRole, JSON.toJSONString(resultDO));
            }
        } catch (Exception e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uid);
        }
        return resultDO;
    }

    /**
     * 本地Guava缓存
     */
    LoadingCache<String, Object> generalLocalCache = CacheBuilder.newBuilder()
            // 设置并发级别为8，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(10)
            // 缓存项在1天内没有被读/写访问，则回收
            .expireAfterAccess(1, TimeUnit.DAYS)
            // 设置20秒自动定时刷新,异步刷新
            .refreshAfterWrite(20, TimeUnit.SECONDS)
            // 设置缓存容器的初始容量为10
            .initialCapacity(1000)
            // 设置缓存最大容量为30000，在缓存项的数目达到限定值之前，缓存就可能进行回收操作
            .maximumSize(30000)
            // 设置要统计缓存的命中率
            .recordStats()
            .build(new CacheLoader<String, Object>() {

                /*
                 * (non-Javadoc)
                 * <p>Description: 加载:同步加载</p>
                 * @see com.google.common.cache.CacheLoader#load(java.lang.Object)
                 */
                @Override
                public Object load(String key) throws InterruptedException, ExecutionException {
                    try {
                        return refreshLocalCache(key);
                    } catch (Exception e) {
                        logger.error("加载数据失败. req key:{}, error : {}", key, e);
                        throw e;
                    }
                }

                /*
                 * (non-Javadoc)
                 * <p>Description: 刷新:异步加载</p>
                 * @see com.google.common.cache.CacheLoader#reload(java.lang.Object, java.lang.Object)
                 */
                @Override
                public ListenableFuture<Object> reload(final String key, Object oldValue) throws InterruptedException,
                        ExecutionException {
                    ListenableFutureTask<Object> task = ListenableFutureTask.create(new Callable<Object>() {
                        public Object call() throws InterruptedException, ExecutionException {
                            try {
                                return refreshLocalCache(key);
                            } catch (Exception e) {
                                logger.error("异步刷新数据失败. req key:{}, error : {}", key, e);
                                throw e;
                            }
                        }
                    });
                    ThreadPool.execute(task);
                    return task;
                }
            });

    private Object refreshLocalCache(String key) {
        String[] keyArr = key.split("_");
        if (key.contains(LOCAL_CACHE_USER_EXP_KEY_PREFIX)) {
            return refreshLocalUserExperience(keyArr[1], keyArr[2]);
        } else if (key.contains(LOCAL_CACHE_USER_ROLE_KEY_PREFIX)) { // 更新角色
            return refreshLocalUserRole(keyArr[1]);
        }
        return null;
    }

    private Integer refreshLocalUserRole(String uid) {
        Integer result = 0;
        try {
            ResultDO<User> userResult = userCacheService.queryUserByUid(uid);
            if (ResultDO.isSuccess(userResult) && userResult.getModule() != null) {
                User user = userResult.getModule();
                if (user != null) {
                    result = user.getRole() != null ? user.getRole() : 0;
                }
            }
        } catch (Exception e) {
            LoggerUtils.error(logger, e, ModuleResultCode.EXPERIENCE, uid);
        }
        return result;
    }

    private ResultDO<UserExperienceDto> refreshLocalUserExperience(String uid, String role) {
        ResultDO<UserExperienceDto> resultDO = ResultDO.buildResult();
        if (role == null) {
            role = "0";
        }
        if (role.equals("0")) {
            resultDO = userExperienceHandler.getUserExperience(uid);
        } else if (role.equals("1")) {
            resultDO = practicerExperienceHandler.getUserExperience(uid);
        } else {
            UserExperienceDto expr = new UserExperienceDto();
            expr.setExperience(0);
            expr.setLevel(1);
            expr.setRole(role);
            expr.setUid(uid);
            resultDO.setModule(expr);
        }
        return resultDO;
    }

    /**
     * 填充有经验记录和没有经验记录的用户到返回结果中
     *
     * @param uids
     * @param dtos
     * @param levels
     * @param role
     */
    private void fillHasOrNoLevelUser(List<String> uids, List<UserExperienceDto> dtos, Map<String, Integer> levels, String role) {
        for (String uid : uids) {
            UserExperienceDto expr = new UserExperienceDto();
            expr.setUid(uid);
            expr.setLevel(levels.get(uid));
            expr.setExperience(null);
            expr.setRole(role);
            dtos.add(expr);
        }
    }

    /**
     * 查询用户和练习生的等级，接口内部封装了用户和练习生的差异
     * 注意：此接口不返回经验
     *
     * @param uids
     * @return
     */
    @Override
    public ResultDO<List<UserExperienceDto>> getUsersLevel(List<String> uids) {
        ResultDO<List<UserExperienceDto>> resultDO = ResultDO.buildResult();
        try {
            ResultDO<List<User>> userResult = userCacheService.queryUserByUids(uids);
            //练习生列表
            List<String> practicerUids = new ArrayList<String>();
            //普通用户列表
            List<String> userUids = new ArrayList<String>();
            classifyUser(userResult, practicerUids, userUids);

            List<UserExperienceDto> dtos = new ArrayList<UserExperienceDto>();
            ResultDO<Map<String, Integer>> usersLevel = userExperienceHandler.getUsersLevel(userUids);
            ResultDO<Map<String, Integer>> practicersLevel = practicerExperienceHandler.getUsersLevel(practicerUids);
            if (usersLevel.isSuccess() && usersLevel.getModule() != null) {
                fillHasOrNoLevelUser(userUids, dtos, usersLevel.getModule(), "0");
            }
            if (practicersLevel.isSuccess() && practicersLevel.getModule() != null) {
                fillHasOrNoLevelUser(practicerUids, dtos, practicersLevel.getModule(), "1");
            }
            resultDO.setModule(dtos);
        } catch (Exception e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uids);
        }
        return resultDO;
    }

    private void classifyUser(ResultDO<List<User>> userResult, List<String> practicerUids, List<String> userUids) {
        if (ResultDO.isSuccess(userResult) && userResult.getModule() != null) {
            for (User user : userResult.getModule()) {
                if (user.getRole() == 0) {
                    userUids.add(user.getUid());
                } else if (user.getRole() == 1) {
                    practicerUids.add(user.getUid());
                }
            }
        }
    }

    /**
     * 获取单个用户等级
     *
     * @param uid
     * @return
     */
    @Override
    public ResultDO<Integer> getUserLevel(String uid) {
        ResultDO<Integer> resultDO = ResultDO.buildResult();
        try {
            checkArgument(ValidatorUtils.isValidUid(uid), "用户ID为空或不符合规则");
            ResultDO<User> userResult = userCacheService.queryUserByUid(uid);
            if (ResultDO.isSuccess(userResult) && userResult.getModule() != null) {
                User user = userResult.getModule();
                if (user.getRole().intValue() == 0) {
                    resultDO = userExperienceHandler.getUserLevel(user.getUid());
                } else if (user.getRole().intValue() == 1) {
                    resultDO = practicerExperienceHandler.getUserLevel(user.getUid());
                }
            } else {
                resultDO.setSuccess(userResult.isSuccess());
                resultDO.setErrorMessage(userResult.getErrorMesssage());
                resultDO.setResultCode(userResult.getResultCode());
            }
        } catch (Exception e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uid);
        }
        return resultDO;
    }

    private String getLocalUserRoleKey(String uid) {
        return LOCAL_CACHE_USER_ROLE_KEY_PREFIX + uid;
    }

    private String getLocalUserExpKey(String uid) {
        return LOCAL_CACHE_USER_EXP_KEY_PREFIX + uid;
    }
}
